{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "3cf7cf1c",
   "metadata": {},
   "source": [
    "这个例子给出了如何Mock某个对象（文件对象）和劫持原生API。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "6d5e86e1",
   "metadata": {},
   "outputs": [],
   "source": [
    "from unittest.mock import MagicMock, patch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "a5d8e0e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "def process_mp4(file_path: str) -> None:\n",
    "    \"\"\" \n",
    "        处理mp4文件，读取前20KB的数据。并且返回前10byte内容。\n",
    "        About open：file.read([size])：size 未指定则返回整个文件，如果文件大小 >2 倍内存则有问题，f.read()读到文件尾时返回\"\"(空字串)。\n",
    "        其中，size为Byte。例如，1024Byte=1KB。\n",
    "    \"\"\"\n",
    "    length = 1024 * 20 # 20KB\n",
    "    with open(file_path, \"rb\") as file_obj:\n",
    "        data = file_obj.read(length)\n",
    "        print(f\"Processed data (first 10 bytes): {data[:10]}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4439f19b",
   "metadata": {},
   "source": [
    "接下来模拟构造一个File对象。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "17fcb7a4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 原生的Python API也可以被模拟\n",
    "mock_open = MagicMock()\n",
    "# File 文件对象的拟制\n",
    "mock_file = MagicMock()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "feb70929",
   "metadata": {},
   "outputs": [],
   "source": [
    "# \n",
    "mock_file.read.return_value = b\"mock mp4 data\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "66c2d300",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 设置 mock_open 的 __enter__ 方法返回 mock_file\n",
    "mock_open.return_value.__enter__.return_value = mock_file\n",
    "# 设置 mock_open 的 __exit__ 方法为一个空的 MagicMock 对象\n",
    "mock_open.return_value.__exit__ = MagicMock()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "1b66035d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 添加一个side_effect来打印调用信息\n",
    "def print_call(*args, **kwargs):\n",
    "    print(f\"open called with args: {args}, kwargs: {kwargs}\")\n",
    "    return mock_open.return_value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "00b49de5",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\" \n",
    "    side_effect看起来非常的类似装饰器，功能就像是AOP的风格。\n",
    "\"\"\"\n",
    "mock_open.side_effect = print_call"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "3f5953a3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "open called with args: ('path/to/your/mp4file.mp4', 'rb'), kwargs: {}\n",
      "Processed data (first 10 bytes): b'mock mp4 d'\n"
     ]
    }
   ],
   "source": [
    "# 使用 patch 来模拟 open 函数\n",
    "with patch(\"__main__.open\", mock_open):\n",
    "    \"\"\" \n",
    "        with patch(\"builtins.open\", mock_open):\n",
    "        这种写法也可以,open 函数是内置函数，它被定义在 builtins 模块中。\n",
    "        相当于全局更换.\n",
    "    \"\"\"\n",
    "    # 调用函数进行测试\n",
    "    process_mp4(\"path/to/your/mp4file.mp4\")\n",
    "\n",
    "    # 验证 mock 对象的行为\n",
    "    mock_open.assert_called_once_with(\"path/to/your/mp4file.mp4\", \"rb\")\n",
    "    mock_file.read.assert_called_once()\n",
    "    mock_open.return_value.__enter__.assert_called_once()\n",
    "    mock_open.return_value.__exit__.assert_called_once()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "my_env",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
